﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using XFControls.Utils;
using XFControls.XFPanels.XFPanelItems;
using System.Collections;

#pragma warning disable 1591
// ReSharper disable InconsistentNaming

namespace XFControls.XFPanels
{
    public partial class XFPanelList : XFPanelBase, IEnumerable
    {
        public bool PanelReceiveSwipe;
        public IXFItem HeaderItem { get; private set; }
        public int PreRenderItems { get; set; }
        public int CacheRenderItems { get; set; }

        public int ItemCount
        {
            get { return _items.Count; }
        }

        protected List<IXFItem> _items;
        protected Dictionary<IXFItem, XFItemAnimation> _animationItems;
        protected IXFItem _selected;
        protected int _selectedY;

        private bool _newSelect;
        private IXFItemExpandable _expander;

        public XFPanelList()
        {
            CacheRenderItems = 75;
            PreRenderItems = 10;
            InitializeComponent();
            _items = new List<IXFItem>();
            _animationItems = new Dictionary<IXFItem, XFItemAnimation>();

            Width = Screen.PrimaryScreen.WorkingArea.Width;
            PanelReceiveSwipe = true;
        }

        public override void Reset()
        {
            base.Reset();
            int height = 0;

            lock (_items)
            {
                foreach (var itm in _items)
                {
                    //itm.Invalidate();
                    itm.ResetHeight();
                    height += itm.GetHeight();
                }
            }
            setHeight(height);
        }

        public new void Resize()
        {
            int height = _items.Sum(itm => itm.GetHeight());
            setHeight(height);
        }

        public int this[IXFItem itm]
        {
            get { return _items.IndexOf(itm); }
        }

        public IXFItem this[int itm]
        {
            get { return _items[itm]; }
        }

        public bool HasItem(Type t)
        {
            return _items.Any(i => i.GetType() == t);
        }

        public bool HasItem(IXFItem item)
        {
            return _items.Contains(item);
        }

        public void ClearItem(IXFItem item)
        {
            ClearItem(this[item]);
        }

        public void ClearItem(int item)
        {
            if (item >= _items.Count || item < 0)
                return;

            IXFItem itm;
            lock (_items)
            {
                itm = _items[item];
                _items.RemoveAt(item);
            }

            if (itm != null)
                itm.Dispose();

            Reset();
        }

        public void ClearItems()
        {
            foreach (var itm in _items)
                itm.Dispose();
            _items.Clear();
            Reset();
        }

        public void ClearItems(int start)
        {
            if (start >= _items.Count)
                return;

            try
            {
                for (int i = start; i < _items.Count; i++)
                {
                    _items[i].Dispose();
                }
            }
            catch (ArgumentOutOfRangeException) { }

            lock (_items)
            {
                if (start >= _items.Count)
                    return;

                _items.RemoveRange(start, _items.Count - start);
            }

            Reset();
        }

        public void ClearItems(int start, int count)
        {
            for (int i = start; i < start + count; i++)
            {
                _items[i].Dispose();
            }

            lock (_items)
                _items.RemoveRange(start, count);

            Reset();
        }

        public void ClearItemsAfterWaiting(XFItemLoading loading)
        {
            if (loading != null)
                ClearItems(this[loading]);
        }

        public void ClearAnimationItem(IXFItem key)
        {
            if (!_animationItems.ContainsKey(key))
                return;

            var itm = _animationItems[key];
            _animationItems.Remove(key);

            itm.Dispose();
        }

        public override void ShowAlphaBar(bool show)
        {
            base.ShowAlphaBar(show);

            if (_alphabar != null)
            {
                _alphabar.LetterChanged += _alphabar_LetterChanged;
            }
        }

        public override void DoSwipe(Point mouseDown, Point mouseUp)
        {
            if (PanelReceiveSwipe && HasBack && (mouseUp.X - mouseDown.X) > (Screen.PrimaryScreen.WorkingArea.Width / 3))
            {
                _curState = States.SlideRight;
                if (!LeftPanel.HasBack && Parent is XFPanelContainer && ((XFPanelContainer)Parent).Header != null)
                    ((XFPanelContainer)Parent).SetHeaderLeftButton(null);
            }


            if (!PanelReceiveSwipe)
            {
                var sel = getSelectedItem();
                if (sel is IXFItemSwipeable)
                    ((IXFItemSwipeable)sel).Swipe(mouseDown, mouseUp);

                PanelReceiveSwipe = true;
            }
            deselectItem();
        }

        public void Add(IXFItem item)
        {
            Add(item, _items.Count + 1);
        }

        public void Add(IXFItem item, int index)
        {
            if (item is XFItemBack)
                HasBack = true;


            item.State = XFItemState.Normal;

            if (item.Parent == null)
                item.Parent = this;
            item.OnAdd();

            if (index > _items.Count)
                _items.Add(item);
            else
                _items.Insert(index, item);


            int height = _items.Sum(itm => itm.GetHeight());
            setHeight(height);

            Damage();
        }

        public void Add(string item)
        {
            if (!string.IsNullOrEmpty(item))
                Add(new XFItemSimpleText(item) { Parent = this });
        }

        public void AddExpander(IXFItemExpandable item)
        {
            AddExpander(item, _items.Count + 1);
        }

        public void AddExpander(IXFItemExpandable item, int index)
        {
            Add(item, index);
            ClearOnScroll();

            if (_expander != null)
                ClearItem(_expander);

            _expander = item;
            OnScroll += (o, e) =>
                        {
                            if (_expander == null) return;

                            var itm = _expander;
                            if (!scrollTooHigh(e.Y) && itm.GetHeight() <= 0) return;

                            itm.SetHeight(e.Y);
                            Resize();
                        };
        }

        public IXFItem AddCopy(IXFItem opt)
        {
            IXFItem itm = opt.GetCopy();
            Add(itm);
            return itm;
        }

        public void AddAnimation(XFItemAnimation item, IXFItem subItem)
        {
            if (!_items.Contains(subItem))
                return;

            _animationItems.Add(subItem, item);
        }

        public void AddHeaderItem(IXFItem item)
        {
            if (HeaderItem != null)
            {
                lock (_items)
                    _items.Remove(HeaderItem);
                HeaderItem.Dispose();
            }
            if (item is XFItemBack)
                HasBack = true;

            if (item.Parent == null)
                item.Parent = this;

            _items.Insert(0, item);

            int height = _items.Sum(itm => itm.GetHeight());
            setHeight(height);

            HeaderItem = item;
        }

        public override void PanelPaint(Graphics g)
        {
            int curY = VirtualLocation.Y;
            bool rendered = false;
            bool animate = false;
            int start = -1;

            if (curY > 0)
                using (Brush b = new SolidBrush(BackColor))
                    g.FillRectangle(b, 0, 0, Width, curY);

            for (int i = 0; i < _items.Count; i++)
            {
                int curHeight = _items[i].GetHeight();

                if (curHeight == 0)
                    continue;

                if ((curY + curHeight) >= 0 && curY <= Screen.PrimaryScreen.WorkingArea.Height)
                {
                    if (start < 0)
                        start = i;

                    rendered = true;

                    if (i != 0 || HeaderItem == null)
                    {
                        RenderItemBuffer(_items[i], false);

                        if (_items[i].Buffer != null)
                            lock (_items[i].Buffer)
                                try { g.DrawImage(_items[i].Buffer, 0, curY); }
                                catch (NullReferenceException) { }

                        if (renderAnimation(_items[i], g, curY))
                            animate = true;
                    }
                }
                else if (rendered)
                {
                    RenderItemBuffer(_items[i], false);
                    break;
                }

                curY += curHeight;

            }

            if (curY <= Screen.PrimaryScreen.WorkingArea.Height)
                using (Brush b = new SolidBrush(BackColor))
                    g.FillRectangle(b, 0, curY, Width, Screen.PrimaryScreen.WorkingArea.Height);

            _hasAnimation = animate;

            if (HeaderItem != null)
                HeaderItem.ItemPaint(g, 0, 0);
        }

        public override void CleanUp()
        {
            base.CleanUp();
            ClearNonVisible();
        }

        private void ClearNonVisible()
        {
            int curY = VirtualLocation.Y;

            lock (_items)
                foreach (IXFItem t in _items)
                {
                    int curHeight = t.GetHeight();

                    if (curHeight == 0)
                        continue;

                    if ((curY + curHeight) >= 0 && curY <= Screen.PrimaryScreen.WorkingArea.Height)
                    {
                        curY += curHeight;
                        continue;
                    }

                    if (t.Buffer != null)
                        lock (t.Buffer)
                        {
                            t.Buffer.Dispose();
                            t.Buffer = null;
                        }

                    curY += curHeight;
                }
        }

        private bool renderAnimation(IXFItem item, Graphics g, int curY)
        {
            bool animate = false;
            if (_animationItems.ContainsKey(item))
            {
                animate = true;
                var anItem = _animationItems[item];

                anItem.Buffer = XFBitmapFactory.GetBitmap(Screen.PrimaryScreen.WorkingArea.Width, anItem.GetHeight());
                using (Graphics gb = Graphics.FromImage(anItem.Buffer))
                {
                    gb.Clear(Color.Purple);
                    anItem.ItemPaint(gb, 0, 0);
                }

                g.DrawAlphaFirstPix(anItem.Buffer, 0, curY);
                lock (anItem)
                {
                    anItem.Buffer.Dispose();
                    anItem.Buffer = null;
                }
            }
            return animate;
        }

        public void RenderItemBuffer(IXFItem item, bool gc)
        {
            //return;
            try
            {
                if (gc)
                {
                    ClearNonVisible();
                    GC.WaitForPendingFinalizers();
                }

                int h = item.GetHeight();
                if (item.Rendered && item.Buffer != null && item.Buffer.Height == h) return;

                int render = _items.Count(i => i.Buffer != null);
                if (render >= CacheRenderItems)
                    ClearNonVisible();

                if (h > 0 && (item.Buffer == null || item.Buffer.Height != h))
                    item.Buffer = XFBitmapFactory.GetBitmap(Width, h);

                if (item.Buffer != null)
                {
                    lock (item.Buffer)
                        using (Graphics gr = Graphics.FromImage(item.Buffer))
                        {
                            gr.Clear(BackColor);
                            item.ItemPaint(gr, 0, 0);
                            item.Rendered = true;
                        }
                }
            }
            catch (OutOfMemoryException)
            {
                if (gc) return;
                RenderItemBuffer(item, true);
            }
            catch (ArgumentNullException) { }
        }

        public void MoveToItem(IXFItem item)
        {
            if (!_items.Contains(item)) return;

            var itm = _items.IndexOf(item);
            int curY = 0;

            for (int i = 0; i < itm; i++)
            {
                curY += _items[i].GetHeight();
            }
            VirtualLocation = new Point(VirtualLocation.X, -curY);

            Invalidate();
        }

        public int MakeItemVisible(IXFItem item)
        {
            return MakeItemVisible(item, item.GetHeight());
        }

        public int MakeItemVisible(IXFItem item, int height)
        {
            int curY = 0;
            lock (_items)
                if (_items.Contains(item))
                {
                    var itm = _items.IndexOf(item);

                    for (int i = 0; i < itm; i++)
                    {
                        curY += _items[i].GetHeight();
                    }

                    int curYLower = curY + height;

                    if (Math.Abs(VirtualLocation.Y) > curY)
                        VirtualLocation = new Point(VirtualLocation.X, -curY);
                    else if (Math.Abs(VirtualLocation.Y) + Parent.Height < curYLower)
                    {
                        int tempY = VirtualLocation.Y;
                        tempY += Math.Abs(VirtualLocation.Y) + Parent.Height - curYLower;
                        VirtualLocation = new Point(VirtualLocation.X, tempY);
                    }
                    Invalidate();
                }
            return curY + VirtualLocation.Y;
        }

        public void OnBlur()
        {
            ClearEffects();
            _newSelect = true;

            if (_selected != null)
                _selected.OnBlur();
            _selected = null;

            deselectItem();
        }

        internal void MouseDown(MouseEventArgs e)
        {
            OnMouseDown(e);
        }

        internal void MouseUp(MouseEventArgs e)
        {
            OnMouseUp(e);
        }

        internal void MouseMove(MouseEventArgs e)
        {
            OnMouseMove(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            switch (_curState)
            {
                case States.Click:

                    var selected = _selected;

                    lock (_items)
                        selectItem(e.Y);

                    var itm = getSelectedItem();
                    if (itm != null)
                        itm.OnMouseDown(toSelectedXCoordinates(e.X), toSelectedYCoordinates(e.Y));

                    if (_selected != selected)
                    {
                        _newSelect = true;
                        if (selected != null)
                            selected.OnBlur();
                    }
                    break;
                case States.Idle:
                    if (_effect == null)
                        OnBlur();
                    break;
                default:
                    OnBlur();
                    break;
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            switch (_curState)
            {
                case States.Click:
                    var itm = getSelectedItem();
                    if (itm == null)
                    {
                        _curState = States.Idle;
                        return;
                    }
                    if (_newSelect)
                        itm.OnFocus();

                    itm.OnMouseUp();
                    switch (itm.GetClickResult())
                    {
                        case XFItemClickResult.Nothing:
                            _curState = States.Idle;
                            break;
                        case XFItemClickResult.SlideLeft:
                            _curState = States.SlideLeft;
                            break;
                        case XFItemClickResult.SlideRight:
                            _curState = States.SlideRight;
                            break;
                        case XFItemClickResult.CustomModal:
                            _curState = States.Modal;
                            break;
                    }

                    deselectItem();
                    break;
            }

            _newSelect = false;
            base.OnMouseUp(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            var itm = getSelectedItem();

            switch (_curState)
            {
                case States.Scroll:
                    OnBlur();
                    Invalidate();
                    break;
                case States.Swipe:
                    if ((Attributes & PanelAttributes.Swipe) != PanelAttributes.Swipe)
                    {
                        OnBlur();
                        Invalidate();
                    }
                    else
                    {
                        itm.OnMouseMove(toSelectedXCoordinates(e.X), toSelectedYCoordinates(e.Y));
                    }
                    break;
            }
        }

        private void deselectItem()
        {
            lock (_items)
                foreach (var itm in _items)
                {
                    if (itm.ItemType == XFItemType.Display)
                        continue;

                    if (itm.State == XFItemState.Selected && itm.ItemType != XFItemType.ClickableNonChange)
                    {
                        itm.Invalidate(true);
                    }
                    itm.State = XFItemState.Normal;
                }

            if ((Attributes & PanelAttributes.Swipe) == PanelAttributes.Swipe)
                Attributes = Attributes ^ PanelAttributes.Swipe;
        }

        private void selectItem(int y)
        {
            _selectedY = VirtualLocation.Y;

            if (HeaderItem != null)
            {
                if (y < _items[0].GetHeight())
                {
                    _items[0].State = XFItemState.Selected;

                    _selected = _items[0];
                    return;
                }
            }

            foreach (IXFItem t in _items)
            {
                int curHeight = t.GetHeight();

                if (y > _selectedY && y < _selectedY + curHeight)
                {
                    switch (t.ItemType)
                    {
                        case XFItemType.Display:
                            OnBlur();
                            return;
                        case XFItemType.Swipable:
                            Attributes |= PanelAttributes.Swipe;
                            break;
                    }

                    if (t.ItemType != XFItemType.ClickableNonChange)
                    {
                        t.Invalidate(true);
                    }
                    t.State = XFItemState.Selected;
                    _selected = t;
                    return;
                }
                _selectedY += curHeight;
            }
        }

        private IXFItem getSelectedItem()
        {
            return _selected;
        }

        private void _alphabar_LetterChanged(char newLetter)
        {
            lock (_items)
                foreach (var item in _items)
                {
                    if (!(item is XFItemSimpleText)) continue;

                    var sText = (XFItemSimpleText)item;
                    if (sText.Text[0] != newLetter) continue;

                    MoveToItem(item);
                    return;
                }
        }

        protected void setHeight(int height)
        {
            try
            {
                int fullScreen = Parent != null ? Parent.Height : Screen.PrimaryScreen.WorkingArea.Height;
                if (height < fullScreen)
                    height = fullScreen;

                Height = height;
            }
            catch (ObjectDisposedException) { }
        }

        private int toSelectedXCoordinates(int x)
        {
            return x - Location.X;
        }

        private int toSelectedYCoordinates(int y)
        {
            return y - (Location.Y + _selectedY);
        }

        protected override void Dispose()
        {
            ClearItems();
            base.Dispose();
        }

        public IEnumerator GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        protected override void Deflate()
        {
            base.Deflate();

            foreach (var item in _items)
            {
                item.Deflate();
            }
        }
    }
}

// ReSharper restore InconsistentNaming
#pragma warning restore 1591
